package edu.fiu.cis.selfhealingdisk;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;

import edu.fiu.cis.selfhealingdisk.IORequest.IOEnum;

public class BlockedUpdates {
	
	public class BlockedDataRequest {
		
		private IORequest expectedEvent;
		private LinkedList<IORequest> delayedEvents = new LinkedList<IORequest>();
		

		public BlockedDataRequest() {
			super();
		}

		
		public IORequest unlockAndDequeue(long currentTime) {
			if (this.expectedEvent != null && 
				this.expectedEvent.getServiceTime() >= 0 &&
				this.expectedEvent.getServiceTime() <= currentTime) {
				
				
				//Lock for any delayed events waiting for the lock
				if (this.delayedEvents.isEmpty()) {
					this.expectedEvent = null;
					return null;
				} else {
					IORequest h = delayedEvents.pop();
					h.setRequestTime(this.expectedEvent.getServiceTime());
					this.expectedEvent = null;
					return h;
				}
				
			} else {
				return null;
			}
		}


		public IORequest getExpectedEvent() {
			return expectedEvent;
		}


		public void setExpectedEvent(IORequest expectedEvent) {
			this.expectedEvent = expectedEvent;
		}

		public void addEvent(IORequest r) {
			this.delayedEvents.add(r);
		}
		
		public boolean isClean() {
			return this.delayedEvents.isEmpty() && this.expectedEvent == null;
		}
		
		public int size() {
			return this.delayedEvents.size();
		}
		
		public String toString() {
			return (this.expectedEvent != null?this.expectedEvent.getRequestId():"BLOCKED") + " blocking " + this.delayedEvents.size() + " events" ;
		}
		
		
	}
	

	private HashMap<Long, BlockedDataRequest> blockedUpdatesTable = new HashMap<Long, BlockedDataRequest>();
	
	/**
	 * Add a new request to this structure. If the parity block associated with the IORequest has a previous queue we
	 * put this new request at the end and wait for the same event. If there is not request associated with the 
	 * parityBlock we create a new list to add the dependency.   
	 * @param parityBlock The first block for the parity unit associated
	 * @param delayed The delayed event
	 * WARNING: this table does not check if the assigned parity block is valid in the current disk layout. 
	 */
	public void blockRequest(long parityBlock,IORequest delayed) {
		if (this.blockedUpdatesTable.containsKey(parityBlock)) {
			this.blockedUpdatesTable.get(parityBlock).addEvent(delayed);
		} else {
			BlockedDataRequest breq = new BlockedDataRequest();
			breq.addEvent(delayed);
			this.blockedUpdatesTable.put(parityBlock,breq);
		}
		
	}

	/**
	 * The parity units can be associated with a request that is outstanding or in a BLOCKED state
	 * @param parityBlock The first block for the parity unit associated
	 * @param expected The event that must be executed to unblock the delayed request (can be ignored if a previous events is currently at queue)
	 * WARNING: this table does not check if the assigned parity block is valid in the current disk layout. 
	 */
	public void setExpectedRequest(long parityBlock, IORequest expected) {
		if (this.blockedUpdatesTable.containsKey(parityBlock)) {
			this.blockedUpdatesTable.get(parityBlock).setExpectedEvent(expected);
		} else {
			BlockedDataRequest bdr = new BlockedDataRequest();
			bdr.setExpectedEvent(expected);
			this.blockedUpdatesTable.put(parityBlock,bdr);
		}
	}
	
	
	/**
	 * Simulates a synchronous execution of all the interruption events until time ti. 
	 * Find all the events that were waiting for a update that was completed (are ready for execution)
	 * If there's most that one event for a particular parity block return only the first one
	 * @return
	 */
	public IORequestList getRequests(long currentTime) {
		IORequestList readyList = new IORequestList();
		ArrayList<Long> rem = new ArrayList<Long>();
		for (Long parityBlock: this.blockedUpdatesTable.keySet()) {
			//Find all the request that are ready
			BlockedDataRequest blockedRequest = this.blockedUpdatesTable.get(parityBlock);
			IORequest ior = blockedRequest.unlockAndDequeue(currentTime);
			
			//There is a event waiting for the lock
			if (ior != null) {
				readyList.add(ior);
			}
			//There are not more outstanding request associated with this block (clear memory)
			if (blockedRequest.isClean()) {
				rem.add(parityBlock);
			}
		}
		
		//Remove all unused entries
		for (Long parityBlock: rem) {
			this.blockedUpdatesTable.remove(parityBlock);
		}
		return readyList;
	}
	
	
	public boolean pendingEventsForParityUnit(long parityBlock) {
		return (this.blockedUpdatesTable.get(parityBlock) != null);
	}
	
	public int size() {
		int s = 0;
		for (Long parityBlock: this.blockedUpdatesTable.keySet()) {
			s += this.blockedUpdatesTable.get(parityBlock).size();
		}
		return s;
	}
	
	
	public boolean isEmpty () {
		return this.blockedUpdatesTable.isEmpty();
	}
	
	public String toString() {
		return blockedUpdatesTable.toString();
	}
	
	
	public static void main(String[] argv) {
		IORequest ior_1 = new IORequest(1,1,8,IOEnum.WRITE,false);
		IORequest ior_2 = new IORequest(2,1,8,IOEnum.WRITE,false);
		IORequest ior_3 = new IORequest(3,1,8,IOEnum.WRITE,false);
		IORequest ior_4 = new IORequest(4,1,8,IOEnum.WRITE,false);
		IORequest ior_5 = new IORequest(5,1,8,IOEnum.WRITE,false);
		
		IORequest b_1 = new IORequest(8,1,8,IOEnum.WRITE,true);
		IORequest b_2 = new IORequest(9,1,8,IOEnum.WRITE,true);
		IORequest b_3 = new IORequest(8,3,8,IOEnum.WRITE,true);
		
		BlockedUpdates btable = new BlockedUpdates();
		btable.setExpectedRequest(8, b_1);
		btable.setExpectedRequest(9, b_2);
		btable.blockRequest(8, ior_1);
		btable.blockRequest(9, ior_4);
		btable.blockRequest(8, ior_2);
		btable.blockRequest(8, ior_3);
		btable.blockRequest(9, ior_5);
		
		System.out.println("Memory footprint:" + btable.size());
		System.out.println("Events:" + btable.getRequests(1));
		System.out.println("Events:" + btable);
		System.out.println("Pending events for parity 8:" + btable.pendingEventsForParityUnit(8));
		System.out.println("Pending events for parity 9:" + btable.pendingEventsForParityUnit(9));
		b_1.setServiceTime(2);
		System.out.println("Request:" + btable.getRequests(1));
		System.out.println("Request:" + btable.getRequests(2));
		System.out.println("Events:" + btable);
		btable.setExpectedRequest(8, b_3);
		System.out.println("Events:" + btable);
		
		
		
	}
	
	
}
